﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Willow.MVC.Search
{
    internal class QueryableSearcher<T>
    {
        protected IEnumerable<ConditionItem> Items { get; set; }

        protected IQueryable<T> Table { get; set; }


        public QueryableSearcher(IQueryable<T> table, IEnumerable<ConditionItem> items)
        {
            Table = table;
            Items = items;
        }


        public IQueryable<T> Search()
        {
            ParameterExpression param = Expression.Parameter(typeof(T), "c");

            var body = GetExpressoininBody(param, Items);

            var expression = Expression.Lambda<Func<T, bool>>(body, param);

            return Table.Where(expression);

        }

        private Expression GetExpressoininBody(ParameterExpression param, IEnumerable<ConditionItem> Items)
        {
            Expression expression = null;

            ///找出没有加分组的项 这些都是And 连接
            var andList = Items.Where(c => string.IsNullOrEmpty(c.OrGroup)).ToList();
            if (andList.Count() != 0)
            {
                foreach (var item in andList)
                {
                    Expression left = GetExpressionByCondition(param, item);
                    expression = expression == null ? left : Expression.AndAlso(left, expression);
                }
            }

            //在找出加组的 组内部以or组合 组与组为and
            var orGroupByList = Items.Where(c => !string.IsNullOrEmpty(c.OrGroup)).GroupBy(c => c.OrGroup);

            foreach (IGrouping<string, ConditionItem> group in orGroupByList)
            {
                if (group.Count() != 0)
                {
                    Expression orGroupByExpAnd = null;
                    foreach (var item in group)//o=>o.id==2||o.name=="3"
                    {
                        Expression left = GetExpressionByCondition(param, item);

                        orGroupByExpAnd = orGroupByExpAnd == null ? left : Expression.OrElse(left, orGroupByExpAnd);
                    }
                    expression = expression == null ? orGroupByExpAnd : Expression.AndAlso(orGroupByExpAnd, expression); ;
                }
            }

            return expression;

        }


        /// <summary>
        /// 根据规则 生成表达式树
        /// </summary>
        /// <param name="param"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        private Expression GetExpressionByCondition(ParameterExpression param, ConditionItem item)
        {
            LambdaExpression exp = GetPropertyLambdaExpression(item, param);//得到属性 如果c.name


            var constant = ChangeTypeToExpression(item, exp.Body.Type);//得到常量 例如 "wfl"

            var selectexp = ExpressionDict[item.Method];//选择连接方法 比如是等于还是like

            Expression returnexp = selectexp(exp.Body, constant); //通过方法连接属性以及参数 得到c.name=="wlf"的表达式

            return returnexp;

        }



        private Expression ChangeTypeToExpression(ConditionItem item, Type type)
        {
            #region 数组
            if (item.Method == QueryMethod.StdIn)
            {
                var arr = (item.Value as Array);
                var expList = new List<Expression>();
                if (arr != null)
                {
                    for (int i = 0; i < arr.Length; i++)
                    {
                        var newvalue = ChangeType(arr.GetValue(i), type);
                        expList.Add(Expression.Constant(newvalue, type));
                    }
                    return Expression.NewArrayInit(type, expList);
                }
            }
            #endregion
            var elementType = GetUnNullableType(type);
            var value = elementType == typeof(Guid) ? Guid.Parse(item.Value.ToString()) : Convert.ChangeType(item.Value, elementType);

            return Expression.Constant(value, type);
        }


        /// <summary>
        /// 类型转换，支持非空类型与可空类型之间的转换
        /// </summary>
        /// <param name="value"></param>
        /// <param name="conversionType"></param>
        /// <returns></returns>
        public static object ChangeType(object value, Type conversionType)
        {
            if (value == null) return null;
            return Convert.ChangeType(value, GetUnNullableType(conversionType));
        }



        private LambdaExpression GetPropertyLambdaExpression(ConditionItem item, ParameterExpression param)
        {
            //获取每级属性如c.StuClass.ClassName
            var props = item.Field.Split('.');
            var typeOfProp = typeof(T);
            Expression exppro = param;
            for (int i = 0; i < props.Length; i++)
            {
                PropertyInfo property = typeOfProp.GetProperty(props[i]);
                if (property == null) return null;
                typeOfProp = property.PropertyType;
                exppro = Expression.Property(exppro, property);
            }

            var lam = Expression.Lambda(exppro, param);
            return lam;
        }

        #region SearchMethod 操作方法

        // <summary>
        /// 获取可空类型的实际类型
        /// </summary>
        /// <param name="conversionType"></param>
        /// <returns></returns>
        private static Type GetUnNullableType(Type conversionType)
        {
            if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                //如果是泛型方法，且泛型类型为Nullable<>则视为可空类型
                //并使用NullableConverter转换器进行转换
                var nullableConverter = new System.ComponentModel.NullableConverter(conversionType);
                conversionType = nullableConverter.UnderlyingType;
            }
            return conversionType;
        }

        private static readonly Dictionary<QueryMethod, Func<Expression, Expression, Expression>> ExpressionDict =
            new Dictionary<QueryMethod, Func<Expression, Expression, Expression>>
                {
                    {
                        QueryMethod.Equal,
                        (left, right) => { return Expression.Equal(left, right); }
                        },
                    {
                        QueryMethod.GreaterThan,
                        (left, right) => { return Expression.GreaterThan(left, right); }
                        },
                    {
                        QueryMethod.GreaterThanOrEqual,
                        (left, right) => { return Expression.GreaterThanOrEqual(left, right); }
                        },
                    {
                        QueryMethod.LessThan,
                        (left, right) => { return Expression.LessThan(left, right); }
                        },
                    {
                        QueryMethod.LessThanOrEqual,
                        (left, right) => { return Expression.LessThanOrEqual(left, right); }
                        },
                    {
                        QueryMethod.Contains,
                        (left, right) =>
                            {
                                if (left.Type != typeof (string)) return null;
                                return Expression.Call(left, typeof (string).GetMethod("Contains"), right);
                            }
                        },
                    {
                        QueryMethod.StdIn,
                        (left, right) =>
                            {
                                if (!right.Type.IsArray) return null;
                                //调用Enumerable.Contains扩展方法
                                MethodCallExpression resultExp =
                                    Expression.Call(
                                        typeof (Enumerable),
                                        "Contains",
                                        new[] {left.Type},
                                        right,
                                        left);

                                return resultExp;
                            }
                        },
                    {
                        QueryMethod.NotEqual,
                        (left, right) => { return Expression.NotEqual(left, right); }
                        },
                    {
                        QueryMethod.StartsWith,
                        (left, right) =>
                            {
                                if (left.Type != typeof (string)) return null;
                                return Expression.Call(left, typeof (string).GetMethod("StartsWith", new[] {typeof (string)}), right);

                            }
                        },
                    {
                        QueryMethod.EndsWith,
                        (left, right) =>
                            {
                                if (left.Type != typeof (string)) return null;
                                return Expression.Call(left, typeof (string).GetMethod("EndsWith", new[] {typeof (string)}), right);
                            }
                        }
                };

        #endregion
    }


}
